package substr

// A Replacer rewrites 'in' into 'out'.
// It is imperative that a Replacer not grow the length of 'in' in any way,
//regardless of cap(in).
// To replace a string with one longer than 'in', a copy must be
//made.
// If no rewriting is done, return 'in'.
// Returning nil or a []byte of length 0 means that 'in' was rewritten to the
//empty string.
type Replacer interface {
	Replace(in []byte) (out []byte)
}

//if there's not enough room in current string, allocate a new one. The Replace*
//functions mustn't assume the returned value is or is not the original.
func repl_new(s *S, diff int) []byte {
	//if the new string will be longer than the current, make one big enough
	if diff <= 0 {
		return s.Super
	}
	lnd := len(s.Super) + diff
	if lnd < cap(s.Super) {
		return s.Super[:lnd]
	}
	return make([]byte, lnd)
}

//given the current substring, the new, old, and replacement strings and the
//offsets into the new string, returns new offsets.
//copy gap from old to new and replace the substring with repl, update Subs
//cursors into old:
//   gap   ignore
// -------[======]
// |      |       |
// last  v[0]    v[1]
//cursors into new:
//   gap   repl
// -------[=====]
// |      |      |
// off  start   end
func do_repl_i(v []int, new, old, repl []byte, off, last int) (o, l int) {
	start := off + (v[0] - last)
	end := start + len(repl)
	//copy gap before current substring
	copy(new[off:start], old[last:v[0]])
	//copy replaced substring
	copy(new[start:end], repl)
	//update Subs and offset
	last = v[1]
	v[0], v[1] = start, end
	return end, last
}

//given the string being operated on and the original (may be the same)
//and the offsets into each respectively, copy any remainder from out to in
func repl_remainder(in, out []byte, off, last int) []byte {
	bs := in[last:]
	copy(out[off:], bs)
	return out
}

//Replace the content of the ith substring by the given Replacer and shift any
//indicies caused by the length of the replacement substring being differnt from
//the original.
//
// Warning: modifies s.Super!
// The operation is inplace and returns itself for chaining.
func (s *S) Replace(i int, R Replacer) *S {
	v := s.Subs[i]
	old_sz := v[1] - v[0]
	slice := s.Super[v[0]:v[1]]
	repl := R.Replace(slice)
	delta := len(repl) - old_sz
	if delta == 0 {
		//don't need to move anything around, just slap the new one in
		copy(slice, repl)
		return s
	}
	new := repl_new(s, delta)
	off, last := do_repl_i(v, new, s.Super, repl, v[0], v[0])
	s.Super = repl_remainder(s.Super, new, off, last)
	//delta != 0 and not last index, shift the rest of the indicies
	if i+1 != len(s.Subs) {
		for _, v := range s.Subs[i+1:] {
			v[0] += delta
			v[1] += delta
		}
	}
	return s
}

func (s *S) replace_filter(negate bool, M Matcher, R Replacer) {
	iln := len(s.Subs)
	if iln == 0 { //nothing to replace
		return
	}

	acc := make([][]byte, iln)
	do := make([]bool, iln)
	one := false
	diff := 0
	for i, v := range s.Subs {
		bs := s.Super[v[0]:v[1]]
		t := M.Match(bs)
		if negate {
			t = !t
		}
		if t {
			acc[i], do[i], one = R.Replace(bs), true, true
			diff += len(acc[i]) - len(bs)
		}
	}

	//there were no replacements, bail
	if !one {
		return
	}

	new := repl_new(s, diff)
	off, last := 0, 0
	for i := 0; i < iln; i++ {
		if do[i] {
			off, last = do_repl_i(s.Subs[i], new, s.Super, acc[i], off, last)
		} else {
			//copy gap and non-replaced substring in one fell swoop
			v := s.Subs[i]
			end := off + (v[1] - last)
			copy(new[off:end], s.Super[last:v[1]])
			v[0] = off + (v[0] - last)
			v[1] = end
			off, last = v[0], end
		}
	}

	//copy after last substr to end of master
	s.Super = repl_remainder(s.Super, new, off, last)
}

//Replace every substring by applying R if it is matched by M.
//
// Warning: modifies s.Super!
// The operation is inplace and returns itself for chaining.
func (s *S) ReplaceIf(M Matcher, R Replacer) *S {
	s.replace_filter(false, M, R)
	return s
}

//Replace every substring by applying R if it is not matched by M.
//
// Warning: modifies s.Super!
// The operation is inplace and returns itself for chaining.
func (s *S) ReplaceIfNot(M Matcher, R Replacer) *S {
	s.replace_filter(true, M, R)
	return s
}

//Replace every substring by applying R to each.
//
// Warning: modifies s.Super!
// The operation is inplace and returns itself for chaining.
func (s *S) ReplaceAll(R Replacer) *S {
	iln := len(s.Subs)
	if iln == 0 {
		return s
	}
	//make replacements
	acc := make([][]byte, iln)
	diff := 0
	for i, v := range s.Subs {
		acc[i] = R.Replace(s.Super[v[0]:v[1]])
		diff += len(acc[i]) - (v[1] - v[0])
	}

	new := repl_new(s, diff)
	//copy replacements in and shifts the gaps between substrs
	off, last := 0, 0
	for i := 0; i < iln; i++ {
		off, last = do_repl_i(s.Subs[i], new, s.Super, acc[i], off, last)
	}

	//copy after last substr to end of master
	s.Super = repl_remainder(s.Super, new, off, last)
	return s
}
